/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.incallui;

import static com.android.contacts.common.compat.CallCompat.Details.PROPERTY_ENTERPRISE_CALL;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.Trace;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.core.content.ContextCompat;

import android.telecom.Call.Details;
import android.telecom.StatusHints;
import android.telecom.TelecomManager;
import android.text.BidiFormatter;
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
import android.view.Display;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;

import com.android.contacts.common.ContactsUtils;
import com.android.dialer.R;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.configprovider.ConfigProviderComponent;
import com.android.dialer.contacts.ContactsComponent;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.dialer.multimedia.MultimediaData;
import com.android.dialer.oem.MotorolaUtils;
import com.android.dialer.phonenumberutil.PhoneNumberHelper;
import com.android.dialer.postcall.PostCall;
import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
import com.android.incallui.ContactInfoCache.ContactCacheEntry;
import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
import com.android.incallui.InCallPresenter.InCallDetailsListener;
import com.android.incallui.InCallPresenter.InCallEventListener;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.incallui.InCallPresenter.IncomingCallListener;
import com.android.incallui.call.CallList;
import com.android.incallui.call.DialerCall;
import com.android.incallui.call.DialerCallListener;
import com.android.incallui.call.state.DialerCallState;
import com.android.incallui.incall.protocol.ContactPhotoType;
import com.android.incallui.incall.protocol.InCallScreen;
import com.android.incallui.incall.protocol.InCallScreenDelegate;
import com.android.incallui.incall.protocol.PrimaryCallState;
import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
import com.android.incallui.incall.protocol.PrimaryInfo;
import com.android.incallui.incall.protocol.SecondaryInfo;
import com.android.incallui.videotech.utils.SessionModificationState;

import java.lang.ref.WeakReference;

/**
 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
 * it along to the fragment.
 */
public class CallCardPresenter
        implements InCallStateListener,
        IncomingCallListener,
        InCallDetailsListener,
        InCallEventListener,
        InCallScreenDelegate,
        DialerCallListener {

    /**
     * Amount of time to wait before sending an announcement via the accessibility manager. When the
     * call state changes to an outgoing or incoming state for the first time, the UI can often be
     * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
     * to ensure that the correct information is announced.
     */
    private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;

    /**
     * Flag to allow the user's current location to be shown during emergency calls.
     */
    private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";

    private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;

    /**
     * Make it possible to not get location during an emergency call if the battery is too low, since
     * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
     * call.
     */
    private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
            "min_battery_percent_for_emergency_location";

    private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;

    private final Context context;
    private final Handler handler = new Handler();

    private DialerCall primary;
    private String primaryNumber;
    private DialerCall secondary;
    private String secondaryNumber;
    private ContactCacheEntry primaryContactInfo;
    private ContactCacheEntry secondaryContactInfo;
    private boolean isFullscreen = false;
    private InCallScreen inCallScreen;
    private boolean isInCallScreenReady;
    private boolean shouldSendAccessibilityEvent;

    private final Runnable sendAccessibilityEventRunnable =
            new Runnable() {
                @Override
                public void run() {
                    shouldSendAccessibilityEvent = !sendAccessibilityEvent(context, getUi());
                    LogUtil.i(
                            "CallCardPresenter.sendAccessibilityEventRunnable",
                            "still should send: %b",
                            shouldSendAccessibilityEvent);
                    if (!shouldSendAccessibilityEvent) {
                        handler.removeCallbacks(this);
                    }
                }
            };

    public CallCardPresenter(Context context) {
        LogUtil.i("CallCardPresenter.constructor", null);
        this.context = Assert.isNotNull(context).getApplicationContext();
    }

    private static boolean hasCallSubject(DialerCall call) {
        return !TextUtils.isEmpty(call.getCallSubject());
    }

    @Override
    public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
        Assert.isNotNull(inCallScreen);
        this.inCallScreen = inCallScreen;

        // Call may be null if disconnect happened already.
        DialerCall call = CallList.getInstance().getFirstCall();
        if (call != null) {
            primary = call;
            if (shouldShowNoteSentToast(primary)) {
                this.inCallScreen.showNoteSentToast();
            }
            call.addListener(this);
            // start processing lookups right away.
            if (!call.isConferenceCall()) {
                startContactInfoSearch(call, true, call.getState() == DialerCallState.INCOMING);
            } else {
                updateContactEntry(null, true);
            }
        }

        onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
    }

    @Override
    public void onInCallScreenReady() {
        LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
        Assert.checkState(!isInCallScreenReady);

        // Contact search may have completed before ui is ready.
        if (primaryContactInfo != null) {
            updatePrimaryDisplayInfo();
        }

        // Register for call state changes last
        InCallPresenter.getInstance().addListener(this);
        InCallPresenter.getInstance().addIncomingCallListener(this);
        InCallPresenter.getInstance().addDetailsListener(this);
        InCallPresenter.getInstance().addInCallEventListener(this);
        isInCallScreenReady = true;

        // Log location impressions
        if (isOutgoingEmergencyCall(primary)) {
            Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
        } else if (isIncomingEmergencyCall(primary) || isIncomingEmergencyCall(secondary)) {
            Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
        }
    }

    @Override
    public void onInCallScreenUnready() {
        LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
        Assert.checkState(isInCallScreenReady);

        // stop getting call state changes
        InCallPresenter.getInstance().removeListener(this);
        InCallPresenter.getInstance().removeIncomingCallListener(this);
        InCallPresenter.getInstance().removeDetailsListener(this);
        InCallPresenter.getInstance().removeInCallEventListener(this);
        if (primary != null) {
            primary.removeListener(this);
        }


        primary = null;
        primaryContactInfo = null;
        secondaryContactInfo = null;
        isInCallScreenReady = false;
    }

    @Override
    public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
        // same logic should happen as with onStateChange()
        onStateChange(oldState, newState, CallList.getInstance());
    }

    @Override
    public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
        Trace.beginSection("CallCardPresenter.onStateChange");
        LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
        if (inCallScreen == null) {
            Trace.endSection();
            return;
        }

        DialerCall primary = null;
        DialerCall secondary = null;

        if (newState == InCallState.INCOMING) {
            primary = callList.getIncomingCall();
        } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
            primary = callList.getOutgoingCall();
            if (primary == null) {
                primary = callList.getPendingOutgoingCall();
            }

            // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
            // highest priority call to display as the secondary call.
            secondary = InCallPresenter.getCallToDisplay(callList, null, true);
        } else if (newState == InCallState.INCALL) {
            primary = InCallPresenter.getCallToDisplay(callList, null, false);
            secondary = InCallPresenter.getCallToDisplay(callList, primary, true);
        }

        LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
        LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
        String primaryNumber = null;
        String secondaryNumber = null;
        if (primary != null) {
            primaryNumber = primary.getNumber();
        }
        if (secondary != null) {
            secondaryNumber = secondary.getNumber();
        }

        final boolean primaryChanged =
                !(DialerCall.areSame(this.primary, primary)
                        && TextUtils.equals(this.primaryNumber, primaryNumber));
        final boolean secondaryChanged =
                !(DialerCall.areSame(this.secondary, secondary)
                        && TextUtils.equals(this.secondaryNumber, secondaryNumber));

        this.secondary = secondary;
        this.secondaryNumber = secondaryNumber;
        DialerCall previousPrimary = this.primary;
        this.primary = primary;
        this.primaryNumber = primaryNumber;

        if (this.primary != null) {
            inCallScreen.updateInCallScreenColors();
        }

        if (primaryChanged && shouldShowNoteSentToast(primary)) {
            inCallScreen.showNoteSentToast();
        }

        // Refresh primary call information if either:
        // 1. Primary call changed.
        // 2. The call's ability to manage conference has changed.
        if (shouldRefreshPrimaryInfo(primaryChanged)) {
            // primary call has changed
            if (previousPrimary != null) {
                previousPrimary.removeListener(this);
            }
            this.primary.addListener(this);

            primaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(context, this.primary);
            updatePrimaryDisplayInfo();
            maybeStartSearch(this.primary, true);
        }

        if (previousPrimary != null && this.primary == null) {
            previousPrimary.removeListener(this);
        }

        if (secondaryChanged) {
            if (this.secondary == null) {
                // Secondary call may have ended.  Update the ui.
                secondaryContactInfo = null;
                updateSecondaryDisplayInfo();
            } else {
                // secondary call has changed
                secondaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(context, this.secondary);
                updateSecondaryDisplayInfo();
                maybeStartSearch(this.secondary, false);
            }
        }

        // Set the call state
        int callState = DialerCallState.IDLE;
        if (this.primary != null) {
            callState = this.primary.getState();
            updatePrimaryCallState();
        } else {
            getUi().setCallState(PrimaryCallState.empty());
        }

        maybeShowManageConferenceCallButton();

        // Hide the end call button instantly if we're receiving an incoming call.
        getUi()
                .setEndCallButtonEnabled(
                        shouldShowEndCallButton(this.primary, callState),
                        callState != DialerCallState.INCOMING /* animate */);

        maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
        Trace.endSection();
    }

    @Override
    public void onDetailsChanged(DialerCall call, Details details) {
        updatePrimaryCallState();

        if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
                != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
            maybeShowManageConferenceCallButton();
        }
    }

    @Override
    public void onDialerCallDisconnect() {
    }

    @Override
    public void onDialerCallUpdate() {
        // No-op; specific call updates handled elsewhere.
    }

    @Override
    public void onWiFiToLteHandover() {
    }

    @Override
    public void onHandoverToWifiFailure() {
    }

    @Override
    public void onInternationalCallOnWifi() {
    }

    @Override
    public void onEnrichedCallSessionUpdate() {
        LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
        updatePrimaryDisplayInfo();
    }

    /**
     * Handles a change to the child number by refreshing the primary call info.
     */
    @Override
    public void onDialerCallChildNumberChange() {
        LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");

        if (primary == null) {
            return;
        }
        updatePrimaryDisplayInfo();
    }

    /**
     * Handles a change to the last forwarding number by refreshing the primary call info.
     */
    @Override
    public void onDialerCallLastForwardedNumberChange() {
        LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");

        if (primary == null) {
            return;
        }
        updatePrimaryDisplayInfo();
        updatePrimaryCallState();
    }

    @Override
    public void onDialerCallUpgradeToVideo() {
    }

    /**
     * Handles a change to the session modification state for a call.
     */
    @Override
    public void onDialerCallSessionModificationStateChange() {
        LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");

        if (primary == null) {
            return;
        }
        getUi()
                .setEndCallButtonEnabled(
                        primary.getVideoTech().getSessionModificationState()
                                != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
                        true /* shouldAnimate */);
        updatePrimaryCallState();
    }

    private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
        if (primary == null) {
            return false;
        }
        return primaryChanged
                || inCallScreen.isManageConferenceVisible() != shouldShowManageConference();
    }

    private void updatePrimaryCallState() {
        if (getUi() != null && primary != null) {
            boolean isWorkCall =
                    primary.hasProperty(PROPERTY_ENTERPRISE_CALL)
                            || (primaryContactInfo != null
                            && primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
            boolean isHdAudioCall =
                    isPrimaryCallActive() && primary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
            boolean isAttemptingHdAudioCall =
                    !isHdAudioCall
                            && !primary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
                            && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(context);

            boolean isBusiness = primaryContactInfo != null && primaryContactInfo.isBusiness;

            // Check for video state change and update the visibility of the contact photo.  The contact
            // photo is hidden when the incoming video surface is shown.
            // The contact photo visibility can also change in setPrimary().
            boolean shouldShowContactPhoto =
                    !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
            getUi()
                    .setCallState(
                            PrimaryCallState.builder()
                                    .setState(primary.getState())
                                    .setIsVideoCall(primary.isVideoCall())
                                    .setSessionModificationState(primary.getVideoTech().getSessionModificationState())
                                    .setDisconnectCause(primary.getDisconnectCause())
                                    .setConnectionLabel(getConnectionLabel())
                                    .setPrimaryColor(
                                            InCallPresenter.getInstance().getThemeColorManager().getPrimaryColor())
                                    .setSimSuggestionReason(getSimSuggestionReason())
                                    .setConnectionIcon(getCallStateIcon())
                                    .setGatewayNumber(getGatewayNumber())
                                    .setCallSubject(shouldShowCallSubject(primary) ? primary.getCallSubject() : null)
                                    .setCallbackNumber(
                                            PhoneNumberHelper.formatNumber(
                                                    context, primary.getCallbackNumber(), primary.getSimCountryIso()))
                                    .setIsWifi(primary.hasProperty(Details.PROPERTY_WIFI))
                                    .setIsConference(
                                            primary.isConferenceCall()
                                                    && !primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE))
                                    .setIsWorkCall(isWorkCall)
                                    .setIsHdAttempting(isAttemptingHdAudioCall)
                                    .setIsHdAudioCall(isHdAudioCall)
                                    .setIsForwardedNumber(
                                            !TextUtils.isEmpty(primary.getLastForwardedNumber())
                                                    || primary.isCallForwarded())
                                    .setShouldShowContactPhoto(shouldShowContactPhoto)
                                    .setConnectTimeMillis(primary.getConnectTimeMillis())
                                    .setIsVoiceMailNumber(primary.isVoiceMailNumber())
                                    .setIsRemotelyHeld(primary.isRemotelyHeld())
                                    .setIsBusinessNumber(isBusiness)
                                    .setSupportsCallOnHold(supports2ndCallOnHold())
                                    .setSwapToSecondaryButtonState(getSwapToSecondaryButtonState())
                                    .setIsAssistedDialed(primary.isAssistedDialed())
                                    .setCustomLabel(null)
                                    .setAssistedDialingExtras(primary.getAssistedDialingExtras())
                                    .build());

            InCallActivity activity =
                    (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
            if (activity != null) {
                activity.onPrimaryCallStateChanged();
            }
        }
    }

    private @ButtonState int getSwapToSecondaryButtonState() {
        if (secondary == null) {
            return ButtonState.NOT_SUPPORT;
        }
        if (primary.getState() == DialerCallState.ACTIVE) {
            return ButtonState.ENABLED;
        }
        return ButtonState.DISABLED;
    }

    /**
     * Only show the conference call button if we can manage the conference.
     */
    private void maybeShowManageConferenceCallButton() {
        getUi().showManageConferenceCallButton(shouldShowManageConference());
    }

    /**
     * Determines if the manage conference button should be visible, based on the current primary
     * call.
     *
     * @return {@code True} if the manage conference button should be visible.
     */
    private boolean shouldShowManageConference() {
        if (primary == null) {
            return false;
        }

        return primary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) && !isFullscreen;
    }

    private boolean supports2ndCallOnHold() {
        DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
        DialerCall incomingCall = CallList.getInstance().getIncomingCall();
        if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
            return incomingCall.can(Details.CAPABILITY_HOLD);
        }
        return true;
    }

    @Override
    public void onCallStateButtonClicked() {
        Intent broadcastIntent = Bindings.get(context).getCallStateButtonBroadcastIntent(context);
        if (broadcastIntent != null) {
            LogUtil.v(
                    "CallCardPresenter.onCallStateButtonClicked",
                    "sending call state button broadcast: " + broadcastIntent);
            context.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
        }
    }

    @Override
    public void onManageConferenceClicked() {
        InCallActivity activity =
                (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
        activity.showConferenceFragment(true);
    }

    @Override
    public void onShrinkAnimationComplete() {
        InCallPresenter.getInstance().onShrinkAnimationComplete();
    }

    private void maybeStartSearch(DialerCall call, boolean isPrimary) {
        // no need to start search for conference calls which show generic info.
        if (call != null && !call.isConferenceCall()) {
            startContactInfoSearch(call, isPrimary, call.getState() == DialerCallState.INCOMING);
        }
    }

    /**
     * Starts a query for more contact data for the save primary and secondary calls.
     */
    private void startContactInfoSearch(
            final DialerCall call, final boolean isPrimary, boolean isIncoming) {
        final ContactInfoCache cache = ContactInfoCache.getInstance(context);

        cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
    }

    private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
        final boolean entryMatchesExistingCall =
                (isPrimary && primary != null && TextUtils.equals(callId, primary.getId()))
                        || (!isPrimary && secondary != null && TextUtils.equals(callId, secondary.getId()));
        if (entryMatchesExistingCall) {
            updateContactEntry(entry, isPrimary);
        } else {
            LogUtil.e(
                    "CallCardPresenter.onContactInfoComplete",
                    "dropping stale contact lookup info for " + callId);
        }

        final DialerCall call = CallList.getInstance().getCallById(callId);
        if (call != null) {
            call.getLogState().contactLookupResult = entry.contactLookupResult;
        }
        if (entry.lookupUri != null) {
            CallerInfoUtils.sendViewNotification(context, entry.lookupUri);
        }
    }

    private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
        if (getUi() == null) {
            return;
        }

        if (entry.photo != null) {
            if (primary != null && callId.equals(primary.getId())) {
                updateContactEntry(entry, true /* isPrimary */);
            } else if (secondary != null && callId.equals(secondary.getId())) {
                updateContactEntry(entry, false /* isPrimary */);
            }
        }
    }

    private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
        if (isPrimary) {
            primaryContactInfo = entry;
            updatePrimaryDisplayInfo();
        } else {
            secondaryContactInfo = entry;
            updateSecondaryDisplayInfo();
        }
    }

    private void updatePrimaryDisplayInfo() {
        if (inCallScreen == null) {
            // TODO: May also occur if search result comes back after ui is destroyed. Look into
            // removing that case completely.
            LogUtil.v(
                    "CallCardPresenter.updatePrimaryDisplayInfo",
                    "updatePrimaryDisplayInfo called but ui is null!");
            return;
        }

        if (primary == null) {
            // Clear the primary display info.
            inCallScreen.setPrimary(PrimaryInfo.empty());
            return;
        }

        // Hide the contact photo if we are in a video call and the incoming video surface is
        // showing.
        boolean showContactPhoto =
                !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());

        // DialerCall placed through a work phone account.
        boolean hasWorkCallProperty = primary.hasProperty(PROPERTY_ENTERPRISE_CALL);

        MultimediaData multimediaData = null;
        if (primary.getEnrichedCallSession() != null) {
            multimediaData = primary.getEnrichedCallSession().getMultimediaData();
        }

        if (primary.isConferenceCall()) {
            LogUtil.v(
                    "CallCardPresenter.updatePrimaryDisplayInfo",
                    "update primary display info for conference call.");

            inCallScreen.setPrimary(
                    PrimaryInfo.builder()
                            .setName(
                                    CallerInfoUtils.getConferenceString(
                                            context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)))
                            .setNameIsNumber(false)
                            .setPhotoType(ContactPhotoType.DEFAULT_PLACEHOLDER)
                            .setIsSipCall(false)
                            .setIsContactPhotoShown(showContactPhoto)
                            .setIsWorkCall(hasWorkCallProperty)
                            .setIsSpam(false)
                            .setIsLocalContact(false)
                            .setAnsweringDisconnectsOngoingCall(false)
                            .setShouldShowLocation(false)
                            .setShowInCallButtonGrid(true)
                            .setNumberPresentation(primary.getNumberPresentation())
                            .build());
        } else if (primaryContactInfo != null) {
            LogUtil.v(
                    "CallCardPresenter.updatePrimaryDisplayInfo",
                    "update primary display info for " + primaryContactInfo);

            String name = getNameForCall(primaryContactInfo);
            String number;

            boolean isChildNumberShown = !TextUtils.isEmpty(primary.getChildNumber());
            boolean isForwardedNumberShown = !TextUtils.isEmpty(primary.getLastForwardedNumber());
            boolean isCallSubjectShown = shouldShowCallSubject(primary);

            if (isCallSubjectShown) {
                number = null;
            } else if (isChildNumberShown) {
                number = context.getString(R.string.child_number, primary.getChildNumber());
            } else if (isForwardedNumberShown) {
                // Use last forwarded number instead of second line, if present.
                number = primary.getLastForwardedNumber();
            } else {
                number = primaryContactInfo.number;
            }

            boolean nameIsNumber = name != null && name.equals(primaryContactInfo.number);

            // DialerCall with caller that is a work contact.
            boolean isWorkContact = (primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
            inCallScreen.setPrimary(
                    PrimaryInfo.builder()
                            .setNumber(number)
                            .setName(primary.updateNameIfRestricted(name))
                            .setNameIsNumber(nameIsNumber)
                            .setLocation(
                                    shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation)
                                            ? primaryContactInfo.location
                                            : null)
                            .setLabel(isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label)
                            .setPhoto(primaryContactInfo.photo)
                            .setPhotoUri(primaryContactInfo.displayPhotoUri)
                            .setPhotoType(primaryContactInfo.photoType)
                            .setIsSipCall(primaryContactInfo.isSipCall)
                            .setIsContactPhotoShown(showContactPhoto)
                            .setIsWorkCall(hasWorkCallProperty || isWorkContact)
                            .setIsSpam(primary.isSpam())
                            .setIsLocalContact(primaryContactInfo.isLocalContact())
                            .setAnsweringDisconnectsOngoingCall(primary.answeringDisconnectsForegroundVideoCall())
                            .setShouldShowLocation(false)
                            .setContactInfoLookupKey(primaryContactInfo.lookupKey)
                            .setMultimediaData(multimediaData)
                            .setShowInCallButtonGrid(true)
                            .setNumberPresentation(primary.getNumberPresentation())
                            .build());
        } else {
            // Clear the primary display info.
            inCallScreen.setPrimary(PrimaryInfo.empty());
        }
    }

    private static boolean shouldShowLocationAsLabel(
            boolean nameIsNumber, boolean shouldShowLocation) {
        if (nameIsNumber) {
            return true;
        }
        if (shouldShowLocation) {
            return true;
        }
        return false;
    }

    private boolean isPotentialEmergencyCall() {
        if (isOutgoingEmergencyCall(primary)) {
            LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
            return true;
        } else if (isIncomingEmergencyCall(primary)) {
            LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
            return true;
        } else if (isIncomingEmergencyCall(secondary)) {
            LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
            return true;
        }
        return false;
    }

    private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
        return call != null && !call.isIncoming() && call.isEmergencyCall();
    }

    private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
        return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
    }

    private boolean hasLocationPermission() {
        return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED;
    }

    private boolean isBatteryTooLowForEmergencyLocation() {
        Intent batteryStatus =
                context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
        int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
        if (status == BatteryManager.BATTERY_STATUS_CHARGING
                || status == BatteryManager.BATTERY_STATUS_FULL) {
            // Plugged in or full battery
            return false;
        }
        int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
        float batteryPercent = (100f * level) / scale;
        long threshold =
                ConfigProviderComponent.get(context)
                        .getConfigProvider()
                        .getLong(
                                CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
                                CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
        LogUtil.i(
                "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
                "percent charged: " + batteryPercent + ", min required charge: " + threshold);
        return batteryPercent < threshold;
    }

    private void updateSecondaryDisplayInfo() {
        if (inCallScreen == null) {
            return;
        }

        if (secondary == null) {
            // Clear the secondary display info.
            inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
            return;
        }

        if (secondary.isMergeInProcess()) {
            LogUtil.i(
                    "CallCardPresenter.updateSecondaryDisplayInfo",
                    "secondary call is merge in process, clearing info");
            inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
            return;
        }

        if (secondary.isConferenceCall()) {
            inCallScreen.setSecondary(
                    SecondaryInfo.builder()
                            .setShouldShow(true)
                            .setName(
                                    CallerInfoUtils.getConferenceString(
                                            context, secondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)))
                            .setProviderLabel(secondary.getCallProviderLabel())
                            .setIsConference(true)
                            .setIsVideoCall(secondary.isVideoCall())
                            .setIsFullscreen(isFullscreen)
                            .build());
        } else if (secondaryContactInfo != null) {
            LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + secondaryContactInfo);
            String name = getNameForCall(secondaryContactInfo);
            boolean nameIsNumber = name != null && name.equals(secondaryContactInfo.number);
            inCallScreen.setSecondary(
                    SecondaryInfo.builder()
                            .setShouldShow(true)
                            .setName(secondary.updateNameIfRestricted(name))
                            .setNameIsNumber(nameIsNumber)
                            .setLabel(secondaryContactInfo.label)
                            .setProviderLabel(secondary.getCallProviderLabel())
                            .setIsVideoCall(secondary.isVideoCall())
                            .setIsFullscreen(isFullscreen)
                            .build());
        } else {
            // Clear the secondary display info.
            inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
        }
    }

    /**
     * Returns the gateway number for any existing outgoing call.
     */
    private String getGatewayNumber() {
        if (hasOutgoingGatewayCall()) {
            return DialerCall.getNumberFromHandle(primary.getGatewayInfo().getGatewayAddress());
        }
        return null;
    }

    /**
     * Returns the label (line of text above the number/name) for any given call. For example,
     * "calling via [Account/Google Voice]" for outgoing calls.
     */
    private String getConnectionLabel() {
        if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
                != PackageManager.PERMISSION_GRANTED) {
            return null;
        }
        StatusHints statusHints = primary.getStatusHints();
        if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
            return statusHints.getLabel().toString();
        }

        if (hasOutgoingGatewayCall() && getUi() != null) {
            // Return the label for the gateway app on outgoing calls.
            final PackageManager pm = context.getPackageManager();
            try {
                ApplicationInfo info =
                        pm.getApplicationInfo(primary.getGatewayInfo().getGatewayProviderPackageName(), 0);
                return pm.getApplicationLabel(info).toString();
            } catch (PackageManager.NameNotFoundException e) {
                LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
                return null;
            }
        }
        return primary.getCallProviderLabel();
    }

    @Nullable
    private SuggestionProvider.Reason getSimSuggestionReason() {
        String value =
                primary.getIntentExtras().getString(SuggestionProvider.EXTRA_SIM_SUGGESTION_REASON);
        if (value == null) {
            return null;
        }
        try {
            return SuggestionProvider.Reason.valueOf(value);
        } catch (IllegalArgumentException e) {
            LogUtil.e("CallCardPresenter.getConnectionLabel", "unknown reason " + value);
            return null;
        }
    }

    private Drawable getCallStateIcon() {
        // Return connection icon if one exists.
        StatusHints statusHints = primary.getStatusHints();
        if (statusHints != null && statusHints.getIcon() != null) {
            Drawable icon = statusHints.getIcon().loadDrawable(context);
            if (icon != null) {
                return icon;
            }
        }

        return null;
    }

    private boolean hasOutgoingGatewayCall() {
        // We only display the gateway information while STATE_DIALING so return false for any other
        // call state.
        // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
        // is also called after a contact search completes (call is not present yet).  Split the
        // UI update so it can receive independent updates.
        if (primary == null) {
            return false;
        }
        return DialerCallState.isDialing(primary.getState())
                && primary.getGatewayInfo() != null
                && !primary.getGatewayInfo().isEmpty();
    }

    /**
     * Gets the name to display for the call.
     */
    private String getNameForCall(ContactCacheEntry contactInfo) {
        String preferredName =
                ContactsComponent.get(context)
                        .contactDisplayPreferences()
                        .getDisplayName(contactInfo.namePrimary, contactInfo.nameAlternative);
        if (TextUtils.isEmpty(preferredName)) {
            return TextUtils.isEmpty(contactInfo.number)
                    ? null
                    : BidiFormatter.getInstance()
                    .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR);
        }
        return preferredName;
    }

    @Override
    public void onSecondaryInfoClicked() {
        if (secondary == null) {
            LogUtil.e(
                    "CallCardPresenter.onSecondaryInfoClicked",
                    "secondary info clicked but no secondary call.");
            return;
        }

        Logger.get(context)
                .logCallImpression(
                        DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
                        primary.getUniqueCallId(),
                        primary.getTimeAddedMs());
        LogUtil.i(
                "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + secondary);
        secondary.unhold();
    }

    @Override
    public void onEndCallClicked() {
        LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + primary);
        if (primary != null) {
            primary.disconnect();
        }
        PostCall.onDisconnectPressed(context);
    }

    /**
     * Handles a change to the fullscreen mode of the in-call UI.
     *
     * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
     */
    @Override
    public void onFullscreenModeChanged(boolean isFullscreenMode) {
        isFullscreen = isFullscreenMode;
        if (inCallScreen == null) {
            return;
        }
        maybeShowManageConferenceCallButton();
    }

    private boolean isPrimaryCallActive() {
        return primary != null && primary.getState() == DialerCallState.ACTIVE;
    }

    private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
        if (primary == null) {
            return false;
        }
        if ((!DialerCallState.isConnectingOrConnected(callState)
                && callState != DialerCallState.DISCONNECTING
                && callState != DialerCallState.DISCONNECTED)
                || callState == DialerCallState.INCOMING) {
            return false;
        }
        if (this.primary.getVideoTech().getSessionModificationState()
                == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
            return false;
        }
        return true;
    }

    @Override
    public void onInCallScreenResumed() {
        updatePrimaryDisplayInfo();

        if (shouldSendAccessibilityEvent) {
            handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
        }
    }

    @Override
    public void onInCallScreenPaused() {
    }

    static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
        AccessibilityManager am =
                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
        if (!am.isEnabled()) {
            LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
            return false;
        }
        if (inCallScreen == null) {
            LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
            return false;
        }
        Fragment fragment = inCallScreen.getInCallScreenFragment();
        if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
            LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
            return false;
        }

        DisplayManager displayManager =
                (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
        Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
        boolean screenIsOn = display.getState() == Display.STATE_ON;
        LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
        if (!screenIsOn) {
            return false;
        }

        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
        inCallScreen.dispatchPopulateAccessibilityEvent(event);
        View view = inCallScreen.getInCallScreenFragment().getView();
        view.getParent().requestSendAccessibilityEvent(view, event);
        return true;
    }

    private void maybeSendAccessibilityEvent(
            InCallState oldState, final InCallState newState, boolean primaryChanged) {
        shouldSendAccessibilityEvent = false;
        if (context == null) {
            return;
        }
        final AccessibilityManager am =
                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
        if (!am.isEnabled()) {
            return;
        }
        // Announce the current call if it's new incoming/outgoing call or primary call is changed
        // due to switching calls between two ongoing calls (one is on hold).
        if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
                || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
                || primaryChanged) {
            LogUtil.i(
                    "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
            shouldSendAccessibilityEvent = true;
            handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
        }
    }

    /**
     * Determines whether the call subject should be visible on the UI. For the call subject to be
     * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
     *
     * @param call The call.
     * @return {@code true} if the subject should be shown, {@code false} otherwise.
     */
    private boolean shouldShowCallSubject(DialerCall call) {
        if (call == null) {
            return false;
        }

        boolean isIncomingOrWaiting =
                primary.getState() == DialerCallState.INCOMING
                        || primary.getState() == DialerCallState.CALL_WAITING;
        return isIncomingOrWaiting
                && !TextUtils.isEmpty(call.getCallSubject())
                && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
                && call.isCallSubjectSupported();
    }

    /**
     * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
     * call with a subject.
     *
     * @param call The call
     * @return {@code true} if the toast should be shown, {@code false} otherwise.
     */
    private boolean shouldShowNoteSentToast(DialerCall call) {
        return call != null
                && hasCallSubject(call)
                && (call.getState() == DialerCallState.DIALING
                || call.getState() == DialerCallState.CONNECTING);
    }

    private InCallScreen getUi() {
        return inCallScreen;
    }

    /**
     * Callback for contact lookup.
     */
    public static class ContactLookupCallback implements ContactInfoCacheCallback {

        private final WeakReference<CallCardPresenter> callCardPresenter;
        private final boolean isPrimary;

        public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
            this.callCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
            this.isPrimary = isPrimary;
        }

        @Override
        public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
            CallCardPresenter presenter = callCardPresenter.get();
            if (presenter != null) {
                presenter.onContactInfoComplete(callId, entry, isPrimary);
            }
        }

        @Override
        public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
            CallCardPresenter presenter = callCardPresenter.get();
            if (presenter != null) {
                presenter.onImageLoadComplete(callId, entry);
            }
        }
    }
}
